package com.sanq.product.books.es.dao.impl;

import com.google.common.collect.Lists;
import com.sanq.product.books.config.EsIndexs;
import com.sanq.product.books.config.Redis;
import com.sanq.product.books.entity.VisiLog;
import com.sanq.product.books.entity.vo.AreaReportVo;
import com.sanq.product.books.entity.vo.BaseReportVo;
import com.sanq.product.books.entity.vo.OsReportVo;
import com.sanq.product.books.es.service.AreaService;
import com.sanq.product.books.es.service.VisiLogSearchService;
import com.sanq.product.config.utils.date.LocalDateUtils;
import com.sanq.product.config.utils.string.StringUtil;
import com.sanq.product.config.utils.web.JsonUtil;
import com.sanq.product.config.utils.web.LogUtil;
import com.sanq.product.redis.service.JedisPoolService;
import com.sanq.product.utils.es.support.impl.BaseSearchSupportImpl;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.search.aggregations.AggregationBuilder;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.cardinality.CardinalityAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.cardinality.ParsedCardinality;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * com.sanq.product.books.es.dao.impl.BooksSearchSupportImpl
 *
 * @author sanq.Yan
 * @date 2019/8/29
 */
@Repository("visiLogSearchService")
public class VisiLogSearchSupportImpl extends BaseSearchSupportImpl<VisiLog> implements VisiLogSearchService {

    @Resource
    private JedisPoolService jedisPoolService;
    @Resource
    private AreaService areaService;

    @Override
    public void saveLogs(long step) {

        String visiLogKey = Redis.ReplaceKey.getVisiLogKey(LocalDateUtils.nowTime());
        List<String> list = jedisPoolService.getList(visiLogKey, 0, step);

        if (0L != step) {
            String indexName = EsIndexs.ReplaceIndex.getVisiLogIndex(LocalDateUtils.nowTime());
            try {
                if (!super.check(indexName)) {
                    this.createIndex();
                }

                List<VisiLog> visiLogList = new ArrayList<>();

                synchronized (VisiLogSearchSupportImpl.class) {
                    list.stream().forEach(item -> {
                        LogUtil.getInstance(VisiLogSearchSupportImpl.class).i(item);
                        VisiLog visiLog = JsonUtil.json2Obj(item, VisiLog.class);

                        Map<String, String> map = areaService.getCityNameByIp(visiLog.getIp());

                        if (map != null) {
                            visiLog.setProvName(map.get("prov_name"));
                            visiLog.setCityName(map.get("city_name"));
                        } else {
                            visiLog.setProvName("未知地区");
                            visiLog.setCityName("未知地区");
                        }

                        visiLogList.add(visiLog);
                    });

                    super.saveList(indexName, EsIndexs.Indexs._DOC, visiLogList);

                    jedisPoolService.rmList(visiLogKey, visiLogList.size());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public boolean createIndex() throws Exception {
        return super.createIndex(EsIndexs.ReplaceIndex.getVisiLogIndex(LocalDateUtils.nowTime()), "{\n" +
                "    \"properties\": {\n" +
                "        \"id\": {\n" +
                "            \"type\": \"long\"\n" +
                "        },\n" +
                "        \"day\": {\n" +
                "            \"type\": \"date\",\n" +
                "            \"format\": \"yyyy-MM-dd||yyyy-MM-dd HH:mm:ss||epoch_millis\"\n" +
                "        },\n" +
                "        \"ip\": {\n" +
                "            \"type\": \"ip\"\n" +
                "        },\n" +
                "        \"userAgent\": {\n" +
                "            \"type\": \"text\"\n" +
                "        },\n" +
                "        \"os\": {\n" +
                "            \"type\": \"keyword\"\n" +
                "        },\n" +
                "        \"url\": {\n" +
                "            \"type\": \"text\"\n" +
                "        },\n" +
                "        \"provName\": {\n" +
                "            \"type\": \"keyword\"\n" +
                "        },\n" +
                "        \"cityName\": {\n" +
                "            \"type\": \"keyword\"\n" +
                "        }\n" +
                "    }\n" +
                "}");
    }

    @Override
    public BaseReportVo getBaseReport(String index) {
        String ipCard = "ip_card";

        BaseReportVo baseReportVo = null;
        try {
            if (!super.check(index)) {
                this.createIndex();
            }

            CardinalityAggregationBuilder cardinalityAggregationBuilder = AggregationBuilders.cardinality(ipCard).field("ip");

            SearchResponse searchResponse = getSearchResponse(index, cardinalityAggregationBuilder);

            if (searchResponse.status().getStatus() == RestStatus.OK.getStatus()) {
                baseReportVo = new BaseReportVo();
                baseReportVo.setViews(StringUtil.toInteger(searchResponse.getHits().getTotalHits()));
                baseReportVo.setIpViews(StringUtil.toInteger(((ParsedCardinality) searchResponse.getAggregations().get(ipCard)).getValue()));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return baseReportVo;
    }

    private SearchResponse getSearchResponse(String index, AggregationBuilder aggregationBuilder) throws Exception {
        SearchRequest searchRequest = new SearchRequest(index).types(EsIndexs.Indexs._DOC);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder().aggregation(aggregationBuilder);
        searchSourceBuilder.size(0);

        searchRequest.source(searchSourceBuilder);

        return super.getClient().search(searchRequest, RequestOptions.DEFAULT);
    }

    @Override
    public List<OsReportVo> getOsReport(String index) {
        String ipCard = "ip_card",
                osTerms = "os_terms";

        try {
            if (!super.check(index)) {
                this.createIndex();
            }

            TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders
                    .terms(osTerms)
                    .field("os")
                    .subAggregation(AggregationBuilders.cardinality(ipCard).field("ip")
                    );

            SearchResponse searchResponse = getSearchResponse(index, termsAggregationBuilder);

            List<OsReportVo> osReportVoList = null;
            if (searchResponse.status().getStatus() == RestStatus.OK.getStatus()) {
                List<? extends Terms.Bucket> buckets = ((ParsedStringTerms) searchResponse.getAggregations().get(osTerms)).getBuckets();

                osReportVoList = new ArrayList<>(buckets.size());
                OsReportVo osReportVo;
                for (int i = 0, size = buckets.size(); i < size; i++) {
                    osReportVo = new OsReportVo();

                    osReportVo.setOs(buckets.get(i).getKeyAsString());
                    osReportVo.setViews(StringUtil.toInteger(buckets.get(i).getDocCount()));
                    osReportVo.setIpViews(StringUtil.toInteger(((ParsedCardinality) buckets.get(i).getAggregations().get(ipCard)).getValue()));
                    osReportVoList.add(osReportVo);
                }
            }
            return osReportVoList;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }

    @Override
    public List<AreaReportVo> getAreaReport(String index) {
        String ipCard = "ip_card",
                provTerms = "prov_terms",
                cityTerms = "city_terms";
        try {
            if (!super.check(index)) {
                this.createIndex();
            }

            TermsAggregationBuilder termsAggregationBuilder =
                    AggregationBuilders.terms(provTerms).field("provName")
                            .subAggregation(
                                    AggregationBuilders.terms(cityTerms).field("cityName")
                                            .subAggregation(
                                                    AggregationBuilders.cardinality(ipCard).field("ip")
                                            )
                            );

            SearchResponse searchResponse = getSearchResponse(index, termsAggregationBuilder);

            List<AreaReportVo> areaReportVoList = Lists.newArrayList();

            if (searchResponse.status().getStatus() == RestStatus.OK.getStatus()) {
                List<? extends Terms.Bucket> buckets = ((ParsedStringTerms) searchResponse.getAggregations().get(provTerms)).getBuckets();

                AreaReportVo areaReportVo;
                for (int i = 0, size = buckets.size(); i < size; i++) {
                    String provName = buckets.get(i).getKeyAsString();  //省份

                    List<? extends Terms.Bucket> cityBuckets = ((ParsedStringTerms) buckets.get(i).getAggregations().get(cityTerms)).getBuckets();
                    for (int j = 0, j_size = cityBuckets.size(); j < j_size; j++) {
                        areaReportVo = new AreaReportVo();

                        areaReportVo.setProvName(provName);
                        areaReportVo.setCityName(cityBuckets.get(j).getKeyAsString());
                        areaReportVo.setViews(StringUtil.toInteger(buckets.get(j).getDocCount()));
                        areaReportVo.setIpViews(StringUtil.toInteger(((ParsedCardinality) cityBuckets.get(j).getAggregations().get(ipCard)).getValue()));

                        areaReportVoList.add(areaReportVo);
                    }
                }
            }
            return areaReportVoList;

        } catch (Exception e) {
            e.printStackTrace();
        }
        return Collections.emptyList();
    }
}
